﻿#region File Description
//-----------------------------------------------------------------------------
// KinectHeadTrack3DDemoGame.cs
//
// Microsoft XNA Community Game Platform
// Copyright (C) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#endregion

using System;
using System.Threading;
using KinectHeadTrack3DDataTypes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;

namespace KinectHeadTrack3DDemo
{

    public class KinectHeadTrack3DDemoGame : Game
    {
        private const float parrallax = 0.5f;

        private GraphicsDeviceManager graphics;
        private SpriteBatch spriteBatch;
        private SpriteFont font;

        KinectHeadTracker tracker=new KinectHeadTracker();


        private Effect effect;

        // strings used to display the current render mode
        private const string gpuSkinningOn = "GPU skinning";
        private const string KinectHeadTrack3DOn = "CPU skinning";

        // this Model uses the GPU based SkinnedEffect
        private Model gpuDude;

        private Model skyBox;
        Texture2D[] skyboxTextures;


        // this is our custom model class that uses CPU skinning and BasicEffect
        private CpuSkinnedModel cpuDude;

        // this handles our animation
        private AnimationPlayer animationPlayer;

        // whether or not to display the CPU skinned model
        private bool displayCpuModel = false;

        // camera settings
        private float cameraRotation = 0;
        private float cameraArc = 0;

        MouseState mouse, mousePrev;


        Viewport leftEye;
        Viewport rightEye;

        

        public KinectHeadTrack3DDemoGame()
        {
            Content.RootDirectory = "Content";

            graphics = new GraphicsDeviceManager(this);


            graphics.IsFullScreen = false;
            //graphics.PreferredBackBufferWidth = 480;
            //graphics.PreferredBackBufferHeight = 800;
            IsMouseVisible = false;

            TargetElapsedTime = TimeSpan.FromTicks(333333);

            // add our frame rate counter
            Components.Add(new FrameRateCounter(this));

            tracker.HeadPositionChanged += new KinectHeadTracker.HeadPositionChangedEventHandler(tracker_HeadPositionChanged);

            tracker.StartHeadtracking();

            currentHeadPosition.Z = 1;


        }

        void tracker_HeadPositionChanged(object sender, Vector3 headPosition)
        {
            /*
            System.Diagnostics.Debug.WriteLine("===Head position changed===");
            System.Diagnostics.Debug.WriteLine("X: "+headPosition.X);
            System.Diagnostics.Debug.WriteLine("Y: "+headPosition.Y);
            System.Diagnostics.Debug.WriteLine("Z: "+headPosition.Z);
            */
            currentHeadPosition = headPosition;
        }

        private Vector3 currentHeadPosition;

        private void CreateViewPorts()
        {
            leftEye = new Viewport
                          {
                              MinDepth = 0,
                              MaxDepth = 1,
                              X = 0,
                              Y = 0,
                              Width = GraphicsDevice.Viewport.Width/2,
                              Height = GraphicsDevice.Viewport.Height,
                          };

            rightEye = new Viewport
                           {
                               MinDepth = 0,
                               MaxDepth = 1,
                               X = GraphicsDevice.Viewport.Width / 2,
                               Y = 0,
                               Width = GraphicsDevice.Viewport.Width/2,
                               Height = GraphicsDevice.Viewport.Height,
                           };
        }

        private void CreateViewPorts2(int w, int h)
        {
            leftEye = new Viewport
            {
                MinDepth = 0,
                MaxDepth = 1,
                X = 0,
                Y = 0,
                Width = w/2,
                Height = h ,
            };

            rightEye = new Viewport
            {
                MinDepth = 0,
                MaxDepth = 1,
                X = w/2,
                Y = 0,
                Width = w/2,
                Height = h ,
            };
        }

        protected override void LoadContent()
        {

            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("font");

            effect = Content.Load<Effect>("effects"); 

            // load our two models
            gpuDude = Content.Load<Model>("dude_gpu");
            cpuDude = Content.Load<CpuSkinnedModel>("dude_cpu");


            skyBox = LoadSkyBox("skybox2", out skyboxTextures);

            // Create an animation player, and start decoding an animation clip.
            animationPlayer = new AnimationPlayer(cpuDude.SkinningData);
            AnimationClip clip = cpuDude.SkinningData.AnimationClips["Take 001"];
            animationPlayer.StartClip(clip);

            // use gestures for input. taps change rendering mode, drags move the camera.
            TouchPanel.EnabledGestures = GestureType.Tap | GestureType.FreeDrag;

            CreateViewPorts();

        }

        private Model LoadSkyBox(string assetName, out Texture2D[] textures)
        {

            Model newModel = Content.Load<Model>(assetName);
            textures = new Texture2D[newModel.Meshes.Count];
            int i = 0;
            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (BasicEffect currentEffect in mesh.Effects)
                    textures[i++] = currentEffect.Texture;


            foreach (ModelMesh mesh in newModel.Meshes)
                foreach (ModelMeshPart meshPart in mesh.MeshParts)
                    meshPart.Effect = effect.Clone();
            
            return newModel;
        }
        
        protected override void Update(GameTime gameTime)
        {
            // exit on the back button
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                Exit();

            // update animations
            animationPlayer.Update(gameTime.ElapsedGameTime, true, Matrix.Identity);

                        mousePrev = mouse;
            mouse = Mouse.GetState();

            if (mouse.LeftButton == ButtonState.Pressed)
            {
                HandleDrag(new Vector2(mouse.X - mousePrev.X, mouse.Y - mousePrev.Y));
            }
            else if (mouse.RightButton == ButtonState.Pressed && mousePrev.RightButton == ButtonState.Released)
            {
                displayCpuModel = !displayCpuModel;
            }

            KeyboardState st = Keyboard.GetState();
            if (st.IsKeyDown(Keys.F))
            {
                graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
                graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
                CreateViewPorts2( graphics.PreferredBackBufferWidth,1080);
                graphics.ToggleFullScreen();


            }

            base.Update(gameTime);
        }

        private void HandleDrag(Vector2 delta)
        {
            cameraRotation += delta.X / 4;
            System.Diagnostics.Debug.WriteLine(cameraArc);
            System.Diagnostics.Debug.WriteLine(delta.Y);
            cameraArc = MathHelper.Clamp(cameraArc - delta.Y / 4, -70, 70);
        }

        protected override void Draw(GameTime gameTime)
        {

            GraphicsDevice.Clear(Color.Black);

            // reset graphics state to be appropriate for drawing our model
            GraphicsDevice.BlendState = BlendState.Opaque;
            GraphicsDevice.RasterizerState = RasterizerState.CullCounterClockwise;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;
            GraphicsDevice.SamplerStates[0] = SamplerState.LinearWrap;


            DrawLeftEye(leftEye);
            DrawRightEye(rightEye);

            base.Draw(gameTime);
        }

        private void DrawLeftEye(Viewport viewport)
        {
            Viewport oldViewPort = GraphicsDevice.Viewport;

            GraphicsDevice.Viewport = viewport;

            float cameraDistance = 60 + (currentHeadPosition.Z - 1)*80;

            cameraArc = MathHelper.Clamp(10 + currentHeadPosition.Y*20, -70, 70);

            //cameraRotation = currentHeadPosition.Y*10;

            Matrix view = Matrix.CreateTranslation(0, -40, 0)*
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation))*
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc))*
                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance), new Vector3(50*currentHeadPosition.X-(parrallax), 0, 0),
                                              Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60.0f - (currentHeadPosition.Z - 1) * 20), GraphicsDevice.Viewport.AspectRatio * 2, 1,
                                                                    1000);

                foreach (ModelMesh mesh in gpuDude.Meshes)
                {
                    foreach (SkinnedEffect effect in mesh.Effects)
                    {
                            effect.SetBoneTransforms(animationPlayer.SkinTransforms);

                        effect.SpecularColor = Vector3.Zero;

                        ConfigureEffectMatrices(effect, Matrix.Identity, view, projection);
                        ConfigureEffectLighting(effect);
                    }

                    mesh.Draw();
                }
      
            DrawSkybox(view, projection);

            // draw our current skinning mode to the screen
      
            GraphicsDevice.Viewport = oldViewPort;
        }

        private void DrawRightEye(Viewport viewport)
        {
            Viewport oldViewPort = GraphicsDevice.Viewport;

            GraphicsDevice.Viewport = viewport;

            float cameraDistance = 60 + (currentHeadPosition.Z - 1) * 80;

            cameraArc = MathHelper.Clamp(10 + currentHeadPosition.Y * 20, -70, 70);

            //cameraRotation = currentHeadPosition.Y*10;

            Matrix view = Matrix.CreateTranslation(0, -40, 0) *
                          Matrix.CreateRotationY(MathHelper.ToRadians(cameraRotation)) *
                          Matrix.CreateRotationX(MathHelper.ToRadians(cameraArc)) *
                          Matrix.CreateLookAt(new Vector3(0, 0, -cameraDistance), new Vector3(50 * currentHeadPosition.X+parrallax, 0, 0),
                                              Vector3.Up);

            Matrix projection = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(60.0f - (currentHeadPosition.Z - 1) * 20), GraphicsDevice.Viewport.AspectRatio * 2, 1,
                                                                    1000);


            foreach (ModelMesh mesh in gpuDude.Meshes)
            {
                foreach (SkinnedEffect effect in mesh.Effects)
                {
                    effect.SetBoneTransforms(animationPlayer.SkinTransforms);

                    effect.SpecularColor = Vector3.Zero;

                    ConfigureEffectMatrices(effect, Matrix.Identity, view, projection);
                    ConfigureEffectLighting(effect);
                }

                mesh.Draw();
            }

            DrawSkybox(view, projection);

            // draw our current skinning mode to the screen
      
            GraphicsDevice.Viewport = oldViewPort;
        }

        protected override void EndRun()
        {
            tracker.StopHeadtracking();
            base.EndRun();
        }

        private void DrawSkybox(Matrix viewMatrix, Matrix projectionMatrix)
        {
            SamplerState ss = new SamplerState();
            ss.AddressU = TextureAddressMode.Clamp;
            ss.AddressV = TextureAddressMode.Clamp;

            DepthStencilState dss = new DepthStencilState();
            dss.DepthBufferEnable = false;
            //device.DepthStencilState = dss;

            Matrix[] skyboxTransforms = new Matrix[skyBox.Bones.Count];
            skyBox.CopyAbsoluteBoneTransformsTo(skyboxTransforms);
            int i = 0;
            foreach (ModelMesh mesh in skyBox.Meshes)
            {
                foreach (Effect currentEffect in mesh.Effects)
                {

                    Matrix worldMatrix = skyboxTransforms[mesh.ParentBone.Index] *Matrix.Identity*Matrix.CreateTranslation(0,25,15)*Matrix.CreateScale(5);
                    
                    currentEffect.CurrentTechnique = currentEffect.Techniques["Textured"];
                    currentEffect.Parameters["World"].SetValue(worldMatrix);
                    currentEffect.Parameters["View"].SetValue(viewMatrix);
                    currentEffect.Parameters["Projection"].SetValue(projectionMatrix);
                    currentEffect.Parameters["xTexture"].SetValue(skyboxTextures[i++]);
                }
                mesh.Draw();
            }

            dss = new DepthStencilState();
            dss.DepthBufferEnable = true;
            //device.DepthStencilState = dss;
        }

        /// <summary>
        /// Helper method to set effect matrices.
        /// </summary>
        private void ConfigureEffectMatrices(IEffectMatrices effect, Matrix world, Matrix view, Matrix projection)
        {
            effect.World = world;
            effect.View = view;
            effect.Projection = projection;
        }

        /// <summary>
        /// Helper to configure effect lighting.
        /// </summary>
        private void ConfigureEffectLighting(IEffectLights effect)
        {
            effect.EnableDefaultLighting();
            effect.DirectionalLight0.Direction = Vector3.Backward;
            effect.DirectionalLight0.Enabled = true;
            effect.DirectionalLight1.Enabled = false;
            effect.DirectionalLight2.Enabled = false;
        }

#if WINDOWS || XBOX
        static void Main()
        {
            using (KinectHeadTrack3DDemoGame game = new KinectHeadTrack3DDemoGame())
            {
                game.Run();
            }
        }
#endif
    }
}
